/*
 * This is an example of a Rust smart contract with two simple, symmetric functions:
 *
 * 1. set_greeting: accepts a greeting, such as "howdy", and records it for the user (account_id)
 *    who sent the request
 * 2. get_greeting: accepts an account_id and returns the greeting saved for it, defaulting to
 *    "Hello"
 *
 * Learn more about writing NEAR smart contracts with Rust:
 * https://github.com/near/near-sdk-rs
 *
 */

use std::collections::HashSet;

use near_sdk::{AccountId, env, near_bindgen, setup_alloc};
// To conserve gas, efficient serialization is achieved through Borsh (http://borsh.io/)
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::borsh::maybestd::collections::HashMap;
use near_sdk::collections::LookupMap;

setup_alloc!();

// Structs in Rust are similar to other languages, and may include impl keyword as shown below
// Note: the names of the structs are not important when calling the smart contract, but the function names are
#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize, Clone)]
pub struct Topic {
    pub id: String,
    pub topic_desc: String,
    pub items: Vec<String>,
    pub item_accout_by_index: HashMap<usize, HashSet<AccountId>>,
    pub total_limit: u128,
    pub current: Vec<AccountId>,
    pub over: bool,

}

#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize)]
pub struct Contract {
    topics: HashMap<String, Topic>,
    search: LookupMap<AccountId, HashSet<String>>,
}

#[near_bindgen]
impl Contract {
    #[init]
    pub fn default() -> Self {
        Self {
            topics: HashMap::new(),
            search: LookupMap::new(b"topics".to_vec()),
        }
    }

    pub fn list(&self, account_id: AccountId) -> Option<Vec<Topic>> {
        let option = self.search.get(&account_id);
        match option {
            Some(ids) => {
                let mut topics: Vec<Topic> = Vec::new();
                for id in ids.iter() {
                    if let Some(topic) = self.topics.get(id) {
                        topics.push(topic.clone());
                    }
                }

                Some(topics)
            }

            _ => { Option::Some(Vec::new()) }
        }
    }

    pub fn create(&mut self, topic_desc: String, items: Vec<String>, total_limit: u128) -> Result<bool, String> {
        let account_id = env::signer_account_id();
        let mut topic = Topic {
            id: format!(
                "{}_{}", account_id, env::block_timestamp()
            ),
            topic_desc,
            items: items.clone(),
            item_accout_by_index: HashMap::new(),
            total_limit,
            current: Vec::new(),
            over: false,
        };

        self.topics.insert(topic.id.clone(), topic.clone());

        Result::Ok(true)
    }
}

/*
 * The rest of this file holds the inline tests for the code above
 * Learn more about Rust tests: https://doc.rust-lang.org/book/ch11-01-writing-tests.html
 *
 * To run from contract directory:
 * cargo test -- --nocapture
 *
 * From project root, to run in combination with frontend tests:
 * yarn test
 *
 */
#[cfg(test)]
mod tests {
    use near_sdk::{testing_env, VMContext};
    use near_sdk::MockedBlockchain;

    use super::*;

    // mock the context for testing, notice "signer_account_id" that was accessed above from env::
    fn get_context(input: Vec<u8>, is_view: bool) -> VMContext {
        VMContext {
            current_account_id: "alice_near".to_string(),
            signer_account_id: "bob_near".to_string(),
            signer_account_pk: vec![0, 1, 2],
            predecessor_account_id: "carol_near".to_string(),
            input,
            block_index: 0,
            block_timestamp: 0,
            account_balance: 0,
            account_locked_balance: 0,
            storage_usage: 0,
            attached_deposit: 0,
            prepaid_gas: 10u64.pow(18),
            random_seed: vec![0, 1, 2],
            is_view,
            output_data_receivers: vec![],
            epoch_height: 19,
        }
    }

    #[test]
    fn set_then_get_greeting() {
        let context = get_context(vec![], false);
        testing_env!(context);
        let mut contract = Contract::default();
        let result = contract.create("test".to_string(), vec!["aa".to_string(), "bb".to_string()], 20);
        assert!(result.unwrap());

        let option = contract.list("alice_near".to_string());
        for topic in option.unwrap() {
            println!("{}",topic);
        }
        // assert_eq!(
        //     "howdy".to_string(),
        //     // contract.get_greeting("bob_near".to_string())
        // );
    }
}
